package com.aliengen.thingple.driverapp.network.api;

import android.content.Context;
import android.telephony.TelephonyManager;
import android.util.Base64;
import android.util.Log;

import com.aliengen.thingple.driverapp.R;
import com.aliengen.thingple.driverapp.data.local.Preferences;
import com.aliengen.thingple.driverapp.network.api.exception.APICallException;
import com.aliengen.thingple.driverapp.network.volley.Network;
import com.android.volley.Response;

import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

/**
 * This class is part of the Thingple mobile application for the CMC168 Handheld reader.
 *
 * Copyright AlienGen Limited 2016. All rights reserved.
 *
 * @author Timothé Mermet-Buffet <tim@aliengen.com>
 */
public class HMACAuthenticator {
    protected Network network;
    protected Context context;

    public HMACAuthenticator(Network network, Context context)
    {
        this.network = network;
        this.context = context;
    }

    public Network getNetwork() {
        return this.network;
    }

    protected String getBaseURL()
    {
        String url = Preferences.get(Preferences.SHARE_API_URL, this.context.getString(R.string.default_api_url));
        if(url.length() == 0)
        {
            Log.e("HMACAuthenticator", "Empty URL set in settings!");
            return "";
        }

        if(url.charAt(url.length()-1) == '/')
        {
            url = url.substring(0, url.length()-1);
        }
        Log.d("HMACAuthenticator", "URL is " + url);
        return url;
    }

    protected String getClientId()
    {
        return Preferences.get(Preferences.SHARE_API_CLIENT_ID, this.context.getString(R.string.default_api_client_id));
    }

    protected String getClientSecret()
    {
        return Preferences.get(Preferences.SHARE_API_CLIENT_SECRET, this.context.getString(R.string.default_api_client_secret));
    }

    public String getDeviceId()
    {
        System.out.println("Retrieving deviceId...");
        TelephonyManager mngr = (TelephonyManager)this.context.getSystemService(Context.TELEPHONY_SERVICE);
        String deviceId = mngr.getDeviceId();
        return deviceId;
     }

    public static final String md5(final String s) {
        try {
            // Create MD5 Hash
            MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.update(s.getBytes("UTF-8"));
            byte messageDigest[] = digest.digest();

            // Create Hex String
            StringBuilder hexString = new StringBuilder();
            for (byte aMessageDigest : messageDigest) {
                String h = Integer.toHexString(0xFF & aMessageDigest);
                while (h.length() < 2) {
                    h = "0" + h;
                }
                hexString.append(h);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch(UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static final String sha256(final String content, final String secret) {
        try {
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
            sha256_HMAC.init(secret_key);

            byte[] messageDigest = sha256_HMAC.doFinal(content.getBytes("UTF-8"));

            // Create Hex String
            StringBuilder hexString = new StringBuilder();
            for (byte aMessageDigest : messageDigest) {
                String h = Integer.toHexString(0xFF & aMessageDigest);
                while (h.length() < 2) {
                    h = "0" + h;
                }
                hexString.append(h);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch(InvalidKeyException e)
        {
            e.printStackTrace();
        } catch(UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }

    protected void authenticate(Request request, String resource, String body) throws APICallException
    {
        Long utc_timestamp = new Long(System.currentTimeMillis() / 1000);

        String method = "GET";
        Integer size = 0;
        if (body != null && body.length() > 0) {
            method = "POST";
            size = body.getBytes().length;
        }

        if(body == null)
        {
            body = new String();
        }

        /**
         * Generate random number
         */
        Random r = new Random();
        Integer nonce = new Integer(r.nextInt(1024));

        /**
         * Generate MD5 and content string
         */
        String md5hash = md5(body);

        String content = method + "\n";
        content += md5hash + "\n";
        content += "application/json\n";
        content += utc_timestamp.toString() + "\n";
        content += nonce.toString() + "\n";
        content += resource + "\n";

        System.out.println("Content: " + content);

        /**
         * Generate HMAC SHA256
         */

        String sha256 = sha256(content, this.getClientSecret());
        String hmacsha256 = new String(android.util.Base64.encode(sha256.getBytes(), Base64.NO_WRAP));

        System.out.println("HMAC: " + sha256);

        /**
         * Set Headers
         */
        String basicAuth = "Thingple " + this.getClientId() + ":" + hmacsha256;

        System.out.println("Authorization: " + basicAuth);

        request.addHeader("Content-Type", "application/json");
        request.addHeader("Content-MD5", md5hash);
        request.addHeader("Nonce", nonce.toString());
        request.addHeader("UTC-Timestamp", utc_timestamp.toString());
        request.addHeader("Content-Length", Integer.toString(size));
        request.addHeader("Authorization", basicAuth);
    }

    public JSONObject post(String resource, JSONObject params, Response.Listener listener, Response.ErrorListener errorListener) throws APICallException
    {
        String complete_url = this.getBaseURL() + resource;
        String body = params.toString();
        Request req = new Request(Request.Method.POST, complete_url, body, listener, errorListener);
        this.authenticate(req, resource, body);
        this.network.addToRequestQueue(req);
        return params;
    }

    public void get(String resource, Response.Listener listener, Response.ErrorListener errorListener) throws APICallException
    {
        String complete_url = this.getBaseURL() + resource;
        Request req = new Request(Request.Method.GET, complete_url, listener, errorListener);
        req.setShouldCache(true);
        this.authenticate(req, resource, "");
        this.network.addToRequestQueue(req);
    }
}
